/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * kernel.cc																*
 *																			*
 * The entry point to the C++ part of the kernel, handles a lot of runtime	*
 * stuff and gets the kernel ready to start interacting with the user.		*
 *																			*
\***************************************************************************/

// includes
#include <stdint.h>
#include <asm/io.h>
#include "kernel.h"
#include "mt.h"
#include "multiboot.h"
#include "pm.h"		
#include "bootmod.h"	
#include "dt.h"
#include "it.h"
#include "timer.h"
#include "heap.h"
#include "iroute.h"
#include "vm.h"

// important stuff
tdtr *g_k_gdt = 0;
titr *g_k_idt = 0;
uint32_t *g_k_vmd = 0;

#define PIC1 0x20
#define PIC2 0xA0
#define ICW1 0x11
#define ICW4 0x01
void init_pics(unsigned int pic1, unsigned int pic2)
{
	/* send ICW1 */
	outb(ICW1, PIC1);
	outb(ICW1, PIC2);

	/* send ICW2 */
	outb(pic1, PIC1 + 1);	/* remap */
	outb(pic2, PIC2 + 1);	/*  pics */

	/* send ICW3 */
	outb(4, PIC1 + 1);	/* IRQ2 -> connection to slave */
	outb(2, PIC2 + 1);

	/* send ICW4 */
	outb(ICW4, PIC1 + 1);
	outb(ICW4, PIC2 + 1);

	/* disable all IRQs - except the timer interrupt for the x86's master 8259 */
	outb(0xFE, PIC1 + 1);
}


void kernel_inthandler_default()
{
	kdbginfo("A interrupt has occured before the kernel was able to correctly handle them.\n");
	asm("cli; hlt; nop; nop;");
	return;
}

// kernel entry point
int main(multiboot_info_t* mb)
{
	struct tbootmod_grub_mod *gm = (struct tbootmod_grub_mod*)mb->mods_addr;
	uint32_t x, y;

	/// --- initialize the physical page manager with the GRUB memory map (pm.cc/pm.h)
	kdbginfo("initiailizing memory manager with grub memory map.\n");
	// --- tell (pm.cc/pm.h) ahead of time where regions of memory are that WE are using! (including the kernel)
	pm_set( (uintptr_t)&__start, ( ( (uintptr_t) &__end - (uintptr_t) &__start ) / 4096 ) + 1, PM_VALID | PM_USED);
	// --- keep memory under 1 MB unavailable to normal kernel operations (PM_LEGACY)
	pm_set( 0x0, 0x100, PM_LEGACY);
	// --- save location of multiboot structure.
	pm_set((uint32_t)mb, 1, PM_VALID | PM_USED);
	// --- save locations where boot modules are stored.
	pm_set(mb->mods_addr, 1, PM_VALID | PM_USED);
	for(x = 0; x < mb->mods_count; ++x)
	{
		pm_set(gm[x].start, (gm[x].end - gm[x].start) / 4096 + ((gm[x].end - gm[x].start) % 4096 > 0 ? 1 : 0), 
				PM_VALID | PM_USED);
	}

	pm_init_with_grub_mmap( mb->mmap_addr, mb->mmap_length );
	pm_set_flush();

	kdbginfo("creating interrupt descriptor table.\n");
	/// create interrupt descriptor table.
	g_k_idt = it_clear(pm_alloc2(2), 0xff);
	for(x = 0; x < 0xff; ++x){
		it_set(g_k_idt, x, (uintptr_t)&kernel_inthandler_default, 0x8, IT_PRESENT);
	}
	it_on(g_k_idt);

	kdbginfo("creating global descriptor table.\n");
	/// create global descriptor table.
	g_k_gdt = dt_clear(pm_alloc2(1), 7);
	dt_nongate(g_k_gdt, 1, 0x0, 0xffffffff, DT_GRAN | DT_BIG | DT_PRESENT | DT_DPL0 | DT_CODE | DT_RW);
	dt_nongate(g_k_gdt, 2, 0x0, 0xffffffff, DT_GRAN | DT_BIG | DT_PRESENT | DT_DPL0 | DT_DATA | DT_RW);
	dt_nongate(g_k_gdt, 3, 0x0, 0xffffffff, DT_GRAN | DT_BIG | DT_PRESENT | DT_DPL0 | DT_DATA | DT_RW);
	dt_nongate(g_k_gdt, 4, 0x0, 0xffffffff, DT_GRAN | DT_BIG | DT_PRESENT | DT_DPL3 | DT_CODE | DT_RW);
	dt_nongate(g_k_gdt, 5, 0x0, 0xffffffff, DT_GRAN | DT_BIG | DT_PRESENT | DT_DPL3 | DT_DATA | DT_RW);
	dt_nongate(g_k_gdt, 6, 0x0, 0xffffffff, DT_GRAN | DT_BIG | DT_PRESENT | DT_DPL3 | DT_DATA | DT_RW);
	dt_on(g_k_gdt);

	kdbginfo("initializing kernel heap.\n");
	kheap_init(pm_alloc2(4), 4096 * 4);
	
	kdbginfo("mapping 8259 hardware to software interrupts.\n");
	init_pics(0x20, 0x28);

	kdbginfo("initializing interrupt routing.\n");
	iroute_initialize();

	kdbginfo("initializing virtual memory.\n");
	/// convert _all_ physical allocations to virtual ones. (little hacky)	
	kdbginfo("patching physical memory manager tree.\n");
	for(x = 0; x < 1024; ++x)
	{
		if(pm_dir[x])
		{
			kdbginfo("patch: %x\n", pm_dir[x]);
			pm_set(pm_dir[x] & ~0xFFF, 1, PM_VALID | PM_USED | PM_INTERNAL);
		}
	}
	kdbginfo("propogating physical allocations as virtual.\n");
	g_k_vmd = vm_make(0);
	vm_map(g_k_vmd, g_k_vmd, (uint32_t)pm_dir, (uint32_t)pm_dir, VM_KERNEL);			// pmdir
	for(x = 0; x < 1024; ++x)
	{
		if(pm_dir[x])
		{
			kdbginfo("ppaav.table:%x(index:%x)\n", pm_dir[x], x);
			vm_map(g_k_vmd, g_k_vmd, pm_dir[x] & ~0xFFF, pm_dir[x] & ~0xFFF, VM_KERNEL);
			for(y = 0; y < 1024; ++y)
			{
				if(((uint32_t*)(pm_dir[x] & ~0xFFF))[y] & PM_USED)
				{
					kdbginfo("ppaav: %x flag(%x)\n", (x << 22) | (y << 12), ((uint32_t*)(pm_dir[x] & ~0xFFF))[y] & 0xFFF);
					vm_map(g_k_vmd, g_k_vmd, (x << 22) | (y << 12), (x << 22) | (y << 12), VM_KERNEL);
				}
			}
		}
	}
	vm_map2(g_k_vmd, g_k_vmd, 0xb8000, 0xb8000, 1, VM_KERNEL);

	kdbginfo("initializing multi-threading.\n");
	initmt((uint32_t)g_k_vmd);
	mtenable();

	kdbginfo("installing default system timer to drive multi-threading.\n");
	install_timer();

	kdbginfo("hooking fault handler for processor.\n");
	fault_init();

	vm_init();

	asm("sti");

	// wait until we arrive in our virtual address space from thread switch.
	kdbginfo("waiting for entrance into address space.\n");
	while(!____sched_curthread->ustack);
	kdbginfo("entered into address space.\n");

	kdbginfo("loading grub modules into memory.\n");
	vm_map(g_k_vmd, g_k_vmd, (uint32_t)mb, (uint32_t)mb, VM_KERNEL);
	bootmod_grub(mb->mods_addr, mb->mods_count);
	
	//sched_threadDescription td;
	//memset(&td, 0, sizeof(td));

	/*
	// ---- testing the 16bit code ----
	// --kmcguire:
	// 1. Allocate some legacy memory (below 1MB) for code, and copy code there.
	memcpy((void*)pm_allocEx(PM_LEGACY | PM_USED, PM_LEGACY, 0, 1), &__b16code, 20);

	// 2. Create a 16BIT thread and allocate legacy memory for it's stack, while using >1MB memory for the kernel stack.
	td.entry = 0x1000;
	td.contextId = 0;
	td.processId = 0;
	td.priority = 0;
	td.ustack = pm_allocEx(PM_LEGACY | PM_USED, PM_LEGACY, 0, 1) + 4096;
	td.kstack = pm_alloc() + 4096;
	td.cs = 0x00;
	td.ds = 0x10;
	td.eflags = SCHED_EFLAGS16;
	td.medaddr = 0;
	THREADID p = sched_create_thread(&td, 0);
	*/
	// loop forever

	while(1);
}
